package com.soriya.nestlive.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yulichang.wrapper.MPJLambdaWrapper;
import com.soriya.nestlive.entity.ChannelEntity;
import com.soriya.nestlive.entity.PageResult;
import com.soriya.nestlive.entity.UserEntity;
import com.soriya.nestlive.entity.VideoEntity;
import com.soriya.nestlive.entity.param.VideoListParam;
import com.soriya.nestlive.entity.param.VideoPublishParam;
import com.soriya.nestlive.entity.vo.VideoDetailVo;
import com.soriya.nestlive.mapper.VideoMapper;
import com.soriya.nestlive.service.VideoService;
import com.soriya.nestlive.service.common.UploadService;
import com.soriya.nestlive.util.excpetion.MyException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@Service
@Slf4j
public class VideoServiceImpl extends ServiceImpl<VideoMapper, VideoEntity> implements VideoService {

    @Autowired
    private UploadService uploadService;

    private final Executor threadPool = Executors.newFixedThreadPool(10);

    @Override
    public PageResult<VideoEntity> pageQuery(Page<VideoEntity> page, VideoListParam param) {
        LambdaQueryWrapper<VideoEntity> wrapper = new LambdaQueryWrapper<>();
        page(page, wrapper);
        return PageResult.from(page);
    }

    @Override
    public boolean publishVideo(VideoPublishParam param) {

        Long channelId = param.getChannelId();

        LambdaQueryWrapper<VideoEntity> wrapper = new LambdaQueryWrapper<VideoEntity>()
                .eq(VideoEntity::getChannelId, channelId)
                .eq(VideoEntity::getTransFlag, 0)
                .last("LIMIT 1");

        VideoEntity video = getOne(wrapper);

        if (video != null) throw new MyException("该频道有视频未转码完成，请等待");

        String source = uploadService.uploadVideo(param.getSource());
        String cover = uploadService.uploadImage(param.getCover());

        Pattern compile = Pattern.compile("(\\d{6}/.*?)\\..*$");
        Matcher matcher = compile.matcher(source);
        boolean b = matcher.find();
        String group = matcher.group(1);

        String path = "/nestlive_resource/target_video/" + group;

        VideoEntity videoEntity = new VideoEntity();
        BeanUtils.copyProperties(param, videoEntity);

        videoEntity.setSource(source);
        videoEntity.setCover(cover);

        save(videoEntity);

        threadPool.execute(() -> transcoding(videoEntity.getId(), source, path));

        return true;
    }

    @Override
    public VideoDetailVo getDetailById(Long id) {
        MPJLambdaWrapper<VideoEntity> wrapper = new MPJLambdaWrapper<VideoEntity>()
                .selectAll(VideoEntity.class)
                .select(UserEntity::getAvatar)
                .select(UserEntity::getNickname)
                .leftJoin(ChannelEntity.class, ChannelEntity::getId, VideoEntity::getChannelId)
                .leftJoin(UserEntity.class, UserEntity::getId, ChannelEntity::getUserId)
                .eq(VideoEntity::getId, id);
        return baseMapper.selectJoinOne(VideoDetailVo.class, wrapper);
    }

    @Override
    public PageResult<VideoDetailVo> getByChannelId(Long channelId, Integer current, Integer page) {
        Page<VideoDetailVo> videoPage = new Page<>();
        MPJLambdaWrapper<VideoEntity> wrapper = new MPJLambdaWrapper<VideoEntity>()
                .selectAll(VideoEntity.class)
                .select(UserEntity::getAvatar)
                .select(UserEntity::getNickname)
                .leftJoin(ChannelEntity.class, ChannelEntity::getId, VideoEntity::getChannelId)
                .leftJoin(UserEntity.class, UserEntity::getId, ChannelEntity::getUserId)
                .eq(VideoEntity::getChannelId, channelId)
                .eq(VideoEntity::getTransFlag, 1)
                .orderByDesc(VideoEntity::getCreateTime);
        baseMapper.selectJoinPage(videoPage, VideoDetailVo.class, wrapper);
        return PageResult.from(videoPage);
    }

    @Override
    public PageResult<VideoDetailVo> getByCategoryId(Long categoryId, Integer current, Integer page) {
        Page<VideoDetailVo> videoPage = new Page<>();
        MPJLambdaWrapper<VideoEntity> wrapper = new MPJLambdaWrapper<VideoEntity>()
                .selectAll(VideoEntity.class)
                .select(UserEntity::getAvatar)
                .select(UserEntity::getNickname)
                .leftJoin(ChannelEntity.class, ChannelEntity::getId, VideoEntity::getChannelId)
                .leftJoin(UserEntity.class, UserEntity::getId, ChannelEntity::getUserId)
                .eq(VideoEntity::getCategoryId, categoryId)
                .eq(VideoEntity::getTransFlag, 1)
                .orderByDesc(VideoEntity::getViewers);
        baseMapper.selectJoinPage(videoPage, VideoDetailVo.class, wrapper);
        return PageResult.from(videoPage);
    }

    private void transcoding(Long videoId, String source, String targetDir) {

        String path = System.getProperty("user.dir").replaceAll("\\\\", "/") + targetDir;

        File file = new File(path);

        if (!file.isDirectory()) {
            boolean mkdirs = file.mkdirs();
        }

        List<String> command = new ArrayList<>();
        command.add("ffmpeg");
        command.add("-i");
        command.add(System.getProperty("user.dir").replaceAll("\\\\", "/") + source);
//        command.add("-bsf:v h264_mp4toannexb -vcodec libx264 -acodec aac -strict -2 -s 360x270 -r 30 -g 60 -start_number 0 -hls_time 5 -hls_list_size 0 -threads 1 -f hls -y low.m3u8");
        command.add("-bsf:v");
        command.add("h264_mp4toannexb");
        command.add("-vcodec");
        command.add("libx264");
        command.add("-acodec");
        command.add("aac");
        command.add("-strict");
        command.add("-2");
        command.add("-s");
        command.add("360x270");
        command.add("-r");
        command.add("30");
        command.add("-g");
        command.add("60");
        command.add("-start_number");
        command.add("0");
        command.add("-hls_time");
        command.add("5");
        command.add("-hls_list_size");
        command.add("0");
        command.add("-threads");
        command.add("1");
        command.add("-f");
        command.add("hls");
        command.add("-y");
        command.add("low.m3u8");

        command.add("-bsf:v");
        command.add("h264_mp4toannexb");
        command.add("-vcodec");
        command.add("libx264");
        command.add("-acodec");
        command.add("aac");
        command.add("-strict");
        command.add("-2");
        command.add("-s");
        command.add("640x480");
        command.add("-r");
        command.add("30");
        command.add("-g");
        command.add("60");
        command.add("-start_number");
        command.add("0");
        command.add("-hls_time");
        command.add("5");
        command.add("-hls_list_size");
        command.add("0");
        command.add("-threads");
        command.add("1");
        command.add("-f");
        command.add("hls");
        command.add("-y");
        command.add("mid.m3u8");

        command.add("-bsf:v");
        command.add("h264_mp4toannexb");
        command.add("-vcodec");
        command.add("libx264");
        command.add("-acodec");
        command.add("aac");
        command.add("-strict");
        command.add("-2");
        command.add("-s");
        command.add("1280x720");
        command.add("-r");
        command.add("30");
        command.add("-g");
        command.add("60");
        command.add("-start_number");
        command.add("0");
        command.add("-hls_time");
        command.add("5");
        command.add("-hls_list_size");
        command.add("0");
        command.add("-threads");
        command.add("1");
        command.add("-f");
        command.add("hls");
        command.add("-y");
        command.add("high.m3u8");
//        command.add("-bsf:v h264_mp4toannexb -vcodec libx264 -acodec aac -strict -2 -s 640x480 -r 30 -g 60 -start_number 0 -hls_time 5 -hls_list_size 0 -threads 1 -f hls -y mid.m3u8");
//        command.add("-bsf:v h264_mp4toannexb -vcodec libx264 -acodec aac -strict -2 -s 1280x720 -r 30 -g 60 -start_number 0 -hls_time 5 -hls_list_size 0 -threads 1 -f hls -y high.m3u8");
        ProcessBuilder processBuilder = new ProcessBuilder(command);
        processBuilder.directory(file);

        VideoEntity videoEntity = new VideoEntity();
        videoEntity.setId(videoId);

        log.info("VideoId: {}", videoId);
        Process process = null;
        try {
            process = processBuilder.start();

//            InputStream inputStream = process.getInputStream();
            InputStream errorStream = process.getErrorStream();
//            new Thread(() -> {
//                try {
//                    byte[] bytes = new byte[1024];
//                    int len;
//                    StringBuilder info = new StringBuilder();
//                    while ((len = inputStream.read(bytes)) != -1) {
//                        info.append(new String(bytes, 0, len));
//                    }
//                    log.info("info: {}", info.toString());
//                } catch (IOException e) {
//                    log.error("FFmepg error");
//                }
//            }).start();

            threadPool.execute(() -> {
                try {
                    byte[] bytes = new byte[1024];
                    int len;
                    StringBuilder error = new StringBuilder();
                    while ((len = errorStream.read(bytes)) != -1) {
                        log.info(new String(bytes, 0, len));
//                        error.append(new String(bytes, 0, len));
                    }

                } catch (IOException e) {
                    log.error("FFmepg error");
                }
            });

            int exitCode = process.waitFor();

            if (exitCode == 0) {
                File index = new File(path, "index.m3u8");
                if (index.createNewFile()) {
                    BufferedWriter bw = new BufferedWriter(new FileWriter(index));
                    bw.write("#EXTM3U");
                    bw.newLine();
                    bw.write("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=400000");
                    bw.newLine();
                    bw.write("low.m3u8");
                    bw.newLine();
                    bw.write("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=800000");
                    bw.newLine();
                    bw.write("mid.m3u8");
                    bw.newLine();
                    bw.write("#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1500000");
                    bw.newLine();
                    bw.write("high.m3u8");
                    bw.flush();
                    bw.close();
                }
                videoEntity.setPath(targetDir + "/index.m3u8");
                videoEntity.setTransFlag(1);
            } else {
                videoEntity.setTransFlag(2);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (process != null) process.destroy();
            updateById(videoEntity);
        }
    }
}
